﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Devonline.Core;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Devonline.Utils
{
    /// <summary>
    /// 简易日志
    /// 文件形式记录, 读取默认配置文件 Logger 节点配置项自动初始化日志
    /// </summary>
    public static class Logger
    {
        /// <summary>
        /// 日志开始记录时间
        /// </summary>
        private static DateTime lastTime = DateTime.Now;
        /// <summary>
        /// 日志记录是否活动状态
        /// </summary>
        private static bool IsActive { get; set; }
        /// <summary>
        /// 当前日志记录配置
        /// </summary>
        private static LoggerOptions Options { get; set; }
        /// <summary>
        /// 日志缓冲区
        /// </summary>
        private static readonly List<LogModel> Buffer = new();

        /// <summary>
        /// 静态构造方法自动开始记录日志
        /// </summary>
        public static void UseLogging(this IServiceCollection services)
        {
            //各种路径初始化
            Options = AppSettings.Configuration.GetSection(nameof(Logger)).Get<LoggerOptions>();
            Options.LogPath = Options.LogPath?.GetAbsolutePath();
            Start();
        }
        /// <summary>
        /// 开始记录日志
        /// </summary>
        public static void Start()
        {
            if (!IsActive)
            {
                IsActive = true;
                Notice($"日志记录已启动, 日志文件保存目录 {Options.LogPath}");
                AppSettings.ExecuteLongRunningLoopTask(() => !IsActive, WriteAsync, async () => await AppSettings.Delay(Options.LogInterval).ConfigureAwait(false)).Start();
            }
        }
        /// <summary>
        /// 停止记录日志, 将会自动退出日志记录线程
        /// </summary>
        public static async Task StopAsync()
        {
            Notice("日志记录已停止");
            IsActive = false;
            do
            {
                await WriteAsync().ConfigureAwait(false);
            } while (Buffer.Count > 0);
        }
        /// <summary>
        /// 记录一条 debug 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Debug(string log)
        {
            Log(LogLevel.Debug, log);
        }
        /// <summary>
        /// 记录一条 info 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Info(string log)
        {
            Log(LogLevel.Info, log);
        }
        /// <summary>
        /// 记录一条 notice 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Notice(string log)
        {
            Log(LogLevel.Notice, log);
        }
        /// <summary>
        /// 记录一条 important 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Warning(string log)
        {
            Log(LogLevel.Warning, log);
        }
        /// <summary>
        /// 记录一条 exception 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Exception(string log, Exception exception)
        {
            Log(LogLevel.Error, $"{log}, 异常详情: {exception.GetMessage()}");
        }
        /// <summary>
        /// 记录一条 error 日志
        /// </summary>
        /// <param name="log"></param>
        public static void Error(string log)
        {
            Log(LogLevel.Error, log);
        }
        /// <summary>
        /// log the process message according to logLevel
        /// </summary>
        /// <param name="logLevel">log level</param>
        /// <param name="log">log message</param>
        private static void Log(LogLevel level, string text)
        {
            try
            {
                var log = new LogModel(level, text);
                if (Options.LogLevel < log.LogLevel || string.IsNullOrEmpty(log.Text))
                {
                    return;
                }

                log.Value = GetLog(log);
                if (string.IsNullOrEmpty(log.Value))
                {
                    return;
                }

                lock (Buffer)
                {
                    if (Options.ConsoleLevel >= log.LogLevel)
                    {
                        //记住原来的控制台颜色
                        var color = Console.ForegroundColor;
                        Console.ForegroundColor = log.LogLevel switch
                        {
                            LogLevel.Error => ConsoleColor.Red,
                            LogLevel.Warning => ConsoleColor.Yellow,
                            LogLevel.Notice => ConsoleColor.Green,
                            LogLevel.Debug => ConsoleColor.Gray,
                            _ => ConsoleColor.White
                        };

                        Console.WriteLine(log.Value);
                        Console.ForegroundColor = color;
                    }

                    var logCount = Buffer.Count(x => x.Text == log.Text);
                    if (logCount == 0 || Options.Repeatable && logCount <= Options.MaxRepeatCount)
                    {
                        Buffer.Add(log);
                    }
                }
            }
            catch (Exception ex)
            {
                Log(LogLevel.Error, $"记录日志出错, 错误信息: {ex.GetInnerException()}");
            }
        }
        /// <summary>
        /// 获取log的内容
        /// </summary>
        /// <param name="log"></param>
        /// <returns></returns>
        private static string GetLog(LogModel log)
        {
            var value = $"时间: {log.CreatedOn.ToString(AppSettings.DEFAULT_DATETIME_FORMAT, CultureInfo.CurrentCulture)}, ";
            if (Options.LogLevel >= log.LogLevel && !string.IsNullOrEmpty(log.Text))
            {
                value += log.LogLevel.GetDisplayName<LogLevel>() + $": {log.Text}";
            }

            return value;
        }
        /// <summary>
        /// write log
        /// </summary>
        private static async Task WriteAsync()
        {
            try
            {
                List<LogModel> writeBuffer = null;
                lock (Buffer)
                {
                    if (Buffer.Count > 0 && (Buffer.Count >= Options.LogCount || (DateTime.Now - lastTime).TotalSeconds >= Options.LogInterval))
                    {
                        writeBuffer = Buffer.ToList();
                        Buffer.Clear();
                    }
                }

                if (writeBuffer != null && writeBuffer.Count > 0)
                {
                    var directory = new DirectoryInfo(Options.LogPath);
                    if (!Directory.Exists(Options.LogPath))
                    {
                        directory = Directory.CreateDirectory(Options.LogPath);
                    }

                    var fileName = Path.Combine(directory.FullName, DateTime.Now.ToString(AppSettings.DEFAULT_DATE_FORMAT, CultureInfo.CurrentCulture) + AppSettings.DEFAULT_LOG_FILE_EXTENSION);
                    Debug($"读取 {writeBuffer.Count} 条日志记录, 即将写入日志文件 {fileName}");
                    var streamWriter = new StreamWriter(fileName, true, Encoding.UTF8);
                    foreach (var log in writeBuffer)
                    {
                        await streamWriter.WriteLineAsync(log.Value).ConfigureAwait(false);
                    }

                    streamWriter.Close();
                    lastTime = DateTime.Now;
                    writeBuffer.Clear();
                    Debug($"日志写入完成");
                }
            }
            catch (Exception ex)
            {
                Exception($"写入日志文件出错", ex);
            }
        }

        /// <summary>
        /// 日志记录配置
        /// </summary>
        class LoggerOptions
        {
            /// <summary>
            /// 构造方法初始化默认参数
            /// </summary>
            public LoggerOptions()
            {
                Repeatable = false;
                LogPath = "logs/";
                LogLevel = LogLevel.Info;

#if DEBUG
                ConsoleLevel = LogLevel.Info;
                LogCount = 1;
                LogInterval = 1;
#else
                ConsoleLevel = LogLevel.Notice;
                LogCount = 10;
                LogInterval = 10;
#endif
            }

            /// <summary>
            /// 日志记录的日志级别, log level 应不高于 console lovel, 否则日志无法显示到 console 中
            /// </summary>
            public LogLevel LogLevel { get; set; }
            /// <summary>
            /// 输出到控制台的日志级别, console level 应不低于 log level, 否则无法显示
            /// </summary>
            public LogLevel ConsoleLevel { get; set; }
            /// <summary>
            /// 是否可记录重复日志
            /// </summary>
            public bool Repeatable { get; set; }
            /// <summary>
            /// 最大可重复次数, Repeatable = true 时有效
            /// </summary>
            public int MaxRepeatCount { get; set; }
            /// <summary>
            /// 日志记录多少条输出文件
            /// </summary>
            public int LogCount { get; set; }
            /// <summary>
            /// 日志记录多长时间输出文件
            /// </summary>
            public int LogInterval { get; set; }
            /// <summary>
            /// 日志目录
            /// </summary>
            public string LogPath { get; set; }
        }

        /// <summary>
        /// 日志对象
        /// </summary>
        class LogModel
        {
            /// <summary>
            /// 内容初始化
            /// </summary>
            /// <param name="logLevel"></param>
            /// <param name="log"></param>
            internal LogModel(LogLevel logLevel, string log)
            {
                LogLevel = logLevel;
                Text = log;
                CreatedOn = DateTime.Now;
            }

            /// <summary>
            /// 日志级别
            /// </summary>
            public LogLevel LogLevel { get; set; }
            /// <summary>
            /// 日志文本
            /// </summary>
            public string Text { get; set; }
            /// <summary>
            /// log content
            /// </summary>
            public string Value { get; set; }
            /// <summary>
            /// 记录时间
            /// </summary>
            public DateTime CreatedOn { get; set; }
        }
    }
}
